#include "OgreStableHeaders.h"

#include "OgreSkeletonFileFormat.h"
#include "OgreSkeletonSerializer.h"
#include "OgreSkeleton.h"
#include "OgreAnimation.h"
#include "OgreAnimationTrack.h"
#include "OgreKeyFrame.h"
#include "OgreBone.h"
#include "OgreString.h"
#include "OgreDataStream.h"
#include "OgreLogManager.h"

namespace Ogre
{
	const long STREAM_OVERHEAD_SIZE = sizeof(uint16) + sizeof(uint32);

	SkeletonSerializer::SkeletonSerializer()
	{
		mVersion = "[Serializer_v1.10]";
	}

	SkeletonSerializer::~SkeletonSerializer()
	{

	}

	void SkeletonSerializer::exportSkeleton(const Skeleton* pSkeleton,
		const String& filename, Endian endianMode)
	{
		std::fstream *f = OGRE_NEW_T(std::fstream, MEMCATEGORY_GENERAL)();
		f->open(filename.c_str(), std::ios::binary | std::ios::out);
		DataStreamPtr stream(OGRE_NEW FileStreamDataStream(f));

		exportSkeleton(pSkeleton, stream, endianMode);

		stream->close();
	}

	void SkeletonSerializer::exportSkeleton(const Skeleton* pSkeleton,
		DataStreamPtr stream, Endian endianMode)
	{
		determineEndianness(endianMode);

		String msg;
		mStream = stream;
		if (!stream->isWriteable())
		{
			OGRE_EXCEPT(Exception::ERR_CANNOT_WRITE_TO_FILE,
				"Unable to write to stream " + stream->getName(),
				"SkeletonSerializer::exportSkeleton");
		}

		writeFileHeader();

		LogManager::getSingleton().logMessage("Exporting bones...");
		writeSkeleton(pSkeleton);
		LogManager::getSingleton().logMessage("Bones exported.");

		unsigned short numAnims = pSkeleton->getNumAnimations();
		LogManager::getSingleton().stream()
			<< "Exporting animations, count = " << numAnims;
		for (unsigned short i = 0; i < numAnims; ++i)
		{
			Animation* pAnim = pSkeleton->getAnimation(i);
			LogManager::getSingleton().stream()
				<< "Exporting animation: " << pAnim->getName();
			writeAnimation(pSkeleton, pAnim);
			LogManager::getSingleton().logMessage("Animation exported.");
		}

		Skeleton::LinkedSkeletonAnimSourceIterator linkIt = 
			pSkeleton->getLinkedSkeletonAnimationSourceIterator();
		while (linkIt.hasMoreElements())
		{
			const LinkedSkeletonAnimationSource& link = linkIt.getNext();
			writeSkeletonAnimationLink(pSkeleton, link);
		}
	}

	void SkeletonSerializer::importSkeleton(DataStreamPtr& stream, Skeleton* pSkel)
	{
		determineEndianness(stream);

		readFileHeader(stream);

		unsigned short streamID;
		while (!stream->eof())
		{
			streamID = readChunk(stream);
			switch (streamID)
			{
			case SKELETON_BONE:
				readBone(stream, pSkel);
				break;
			case SKELETON_BONE_PARENT:
				readBoneParent(stream, pSkel);
				break;
			case SKELETON_ANIMATION:
				readAnimation(stream, pSkel);
				break;
			case SKELETON_ANIMATION_LINK:
				readSkeletonAnimationLink(stream, pSkel);
				break;
			}
		}

		pSkel->setBindingPose();
	}

	void SkeletonSerializer::writeSkeleton(const Skeleton* pSkel)
	{
		unsigned short numBones = pSkel->getNumBones();
		unsigned short i;
		for (i = 0; i < numBones; ++i)
		{
			Bone* pBone = pSkel->getBone(i);
			writeBone(pSkel, pBone);
		}
		for (i = 0; i < numBones; ++i)
		{
			Bone* pBone = pSkel->getBone(i);
			unsigned short handle = pBone->getHandle();
			Bone* pParent = (Bone*)pBone->getParent();
			if (pParent != NULL)
			{
				writeBoneParent(pSkel, handle, pParent->getHandle());
			}
		}
	}

	void SkeletonSerializer::writeBone(const Skeleton* pSkel, const Bone* pBone)
	{
		writeChunkHeader(SKELETON_BONE, calcBoneSize(pSkel, pBone));

		unsigned short handle = pBone->getHandle();

		writeString(pBone->getName());
		writeShorts(&handle, 1);
		writeObject(pBone->getPosition());
		writeObject(pBone->getOrientation());
		if (pBone->getScale() != Vector3::UNIT_SCALE)
		{
			writeObject(pBone->getScale());
		}
	}

	void SkeletonSerializer::writeBoneParent(const Skeleton* pSkel,
		unsigned short boneId, unsigned short parentId)
	{
		writeChunkHeader(SKELETON_BONE_PARENT, calcBoneParentSize(pSkel));

		writeShorts(&boneId, 1);
		writeShorts(&parentId, 1);
	}

	void SkeletonSerailizer::writeAnimation(const Skeleton* pSkel,
		const Animation* anim)
	{
		writeChunkHeader(SKELETON_ANIMATION, calcAnimationSize(pSkel, anim));

		writeString(anim->getName());
		float len = anim->getLength();
		writeFloats(&len, 1);

		Animation::NodeTrackIterator trackIt = anim->getNodeTrackIterator();
		while (trackIt.hasMoreElements())
		{
			writeAnimationTrack(pSkel, trackIt.getNext());
		}
	}

	void SkeletonSerializer::writeAnimationTrack(const Skeleton* pSkel,
		const NodeAnimationTrack* track)
	{
		writeChunkHeader(SKELETON_ANIMATION_TRACK, calcAnimationTrackSize(pSkel, track));

		Bone* bone = (Bone*)track->getAssociatedNode();
		unsigned short boneid = bone->getHandle();
		writeShorts(&boneid, 1);

		for (unsigned short i = 0; i < track->getNumKeyFrames(); ++i)
		{
			writeKeyFrame(pSkel, track->getNodeKeyFrame(i));
		}
	}

	void SkeletonSerializer::writeKeyFrame(const Skeleton* pSkel,
		const TransformKeyFrame* key)
	{
		writeChunkHeader(SKELETON_ANIMATION_TRACK_KEYFRAME,
			calcKeyFrameSize(pSkel, key));

		float time = key->getTime();
		writeFloats(&time, 1);
		writeObject(key->getRotation());
		writeObject(key->getTranslation());
		if (key->getScale() != Vector3::UNIT_SCALE)
		{
			writeObject(key->getScale());
		}
	}

	size_t SkeletonSerializer::calcBoneSize(const Skeleton* pSkel,
		const Bone* pBone)
	{
		size_t size = STREAM_OVERHEAD_SIZE;

		size += sizeof(unsigned short);
		size += sizeof(float) * 3;
		size += sizeof(float) * 4;
		if (pBone->getScale() != Vector3::UNIT_SCALE)
		{
			size += sizeof(float) * 3;
		}

		return size;
	}

	size_t SkeletonSerializer::calcBoneSizeWithoutScale(const Skeleton* pSkel,
		const Bone* pBone)
	{
		size_t size = STREAM_OVERHEAD_SIZE;

		size += sizeof(unsigned short);
		size += sizeof(float) * 3;
		size += sizeof(float) * 4;

		return size;
	}

	size_t SkeletonSerializer::calcBoneParentSize(const Skeleton* pSkel)
	{
		size_t size = STREAM_OVERHEAD_SIZE;

		size += sizeof(unsigned short);
		size += sizeof(unsigned short);

		return size;
	}

	size_t SkeletonSerializer::calcAnimationSize(const Skeleton* pSkel,
		const Animation* pAnim)
	{
		size_t size = STREAM_OVERHEAD_SIZE;

		size += pAnim->getName().length() + 1;
		size += sizeof(float);
		Animation::NodeTrackIterator trackIt = pAnim->getNodeTrackIterator();
		while (trackIt.hasMoreElements())
		{
			size += calcAnimationTrackSize(pSkel, trackIt.getNext());
		}

		return size;
	}

	size_t SkeletonSerializer::calcAnimationTrackSize(const Skeleton* pSkel,
		const NodeAnimationTrack* pTrack)
	{
		size_t size = STREAM_OVERHEAD_SIZE;

		size += sizeof(unsigned short);
		for (unsigned short i = 0; i < pTrack->getNumKeyFrames(); ++i)
		{
			size += calcKeyFrameSize(pSkel, pTrack->getNodeKeyFrame(i));
		}

		return size;
	}

	size_t SkeletonSerializer::calcKeyFrameSize(const Skeleton* pSkel,
		const TransformKeyFrame* pKey)
	{
		size_t size = STREAM_OVERHEAD_SIZE;

		size_t += sizeof(float);
		size_t += sizeof(float) * 4;
		size_t += sizeof(float) * 3;
		if (pKey->getScale() != Vector3::UNIT_SCALE)
		{
			size += sizeof(float) * 3;
		}

		return size;
	}

	size_t SkeletonSerializer::calcKeyFrameSizeWithoutScale(const Skeleton* pSkel,
		const TransformKeyFrame* pKey)
	{
		size_t size = STREAM_OVERHEAD_SIZE;

		size += sizeof(float);
		size += sizeof(float) * 4;
		size += sizeof(float) * 3;
		return size;
	}

	void SkeletonSerializer::readBone(DataStreamPtr& stream, Skeleton* pSkel)
	{
		String name = readString(stream);
		unsigned short handle;
		readShorts(stream, &handle, 1);

		Bone* pBone = pSkel->createBone(name, handle);
		Vector3 pos;
		readObject(stream, pos);
		pBone->setPosition(pos);
		Quaternion q;
		readObject(stream, q);
		pBone->setOrientation(q);
		if (mCurrentstreamLen > calcBoneSizeWithoutScale(pSkel, pBone))
		{
			Vector3 scale;
			readObject(stream, scale);
			pBone->setScale(scale);
		}
	}

	void SkeletonSerializer::readBoneParent(DataStreamPtr& stream, Skeleton* pSkel)
	{
		Bone* child, *parent;
		unsigned short childHandle, parentHandle;

		readShorts(stream, &childHandle, 1);
		readShorts(stream, &parentHandle, 1);
		parent = pSkel->getBone(parentHandle);
		child = pSkel->getBone(childHandle);

		parent->addChild(child);
	}

	void SkeletonSerializer::readAnimation(DataStreamPtr& stream, Skeleton* pSkel)
	{
		String name;
		name = readString(stream);
		float len;
		readFloats(stream, &len, 1);
		Animation* pAnim = pSkel->createAnimation(name, len);

		if (!stream->eof())
		{
			unsigned short streamID = readChunk(stream);
			while (streamID == SKELETON_ANIMATION_TRACK && !stream->eof())
			{
				readAnimationTrack(stream, pAnim, pSkel);

				if (!stream->eof())
				{
					streamID = readChunk(stream);
				}
			}
			if (!stream->eof())
			{
				stream->skip(-STREAM_OVERHEAD_SIZE);
			}
		}
	}

	void SkeletonSerializer::readAnimationTrack(DataStreamPtr& stream, Animation* anim,
		Skeleton* pSkel)
	{
		unsigned short boneHandle;
		readShorts(stream, &boneHandle, 1);

		Bone* targetBone = pSkel->getBone(boneHandle);
		NodeAnimationTrack* pTrack = anim->createNodeTrack(boneHandle, targetBone);
		if (!stream->eof())
		{
			unsigned short streamID = readChunk(stream);
			while (streamID == SKELETON_ANIMATION_TRACK_KEYFRAME && !stream->eof())
			{
				readKeyFrame(stream, pTrack, pSkel);

				if (!stream->eof())
				{
					streamID = readChunk(stream);
				}
			}
			if (!stream->eof())
			{
				stream->skip(-STREAM_OVERHEAD_SIZE);
			}
		}
	}

	void SkeletonSerializer::readKeyFrame(DataStreamPtr& stream, NodeAnimationTrack* track,
		Skeleton* pSkel)
	{
		float time;
		readFloats(stream, &time, 1);

		TransformKeyFrame* kf = track->createNodeKeyFrame(time);

		Quaternion rot;
		readObject(stream, rot);
		kf->setRotation(rot);
		Vector3 trans;
		readObject(stream, trans);
		kf->setTranslate(trans);
		if (mCurrentstreamLen > calcKeyFrameSizeWithoutScale(pSkel, kf))
		{
			Vector3 scale;
			readObject(stream, scale);
			kf->setScale(scale);
		}
	}

	void SkeletonSerializer::writeSkeletonAnimationLink(const Skeleton* pSkel,
		const LinkedSkeletonAnimationSource& link)
	{
		writeChunkHeader(SKELETON_ANIMATION_LINK,
				calcSkeletonAnimationLinkSize(pSkel, link));

		writeString(link.skeletonName);
		writeFloats(&(link.scale), 1);
	}

	size_t SkeletonSerializer::calcSkeletonAnimationLinkSize(const Skeleton* pSkel,
		const LinkedSkeletonAnimationSource& link)
	{
		size_t size = STREAM_OVERHEAD_SIZE;

		size += link.skeletonName.length() + 1;
		size += sizeof(float);

		return size;
	}

	void SkeletonSerializer::readSkeletonAnimationLink(DataStreamPtr& stream,
		Skeleton* pSkel)
	{
		String skelName = readString(stream);
		float scale;
		readFloats(stream, &scale, 1);

		pSkel->addLinkedSkeletonAnimationSource(skelName, scale);
	}

}
